/* eslint-disable no-bitwise,no-nested-ternary */
const fs = require('fs-extra');
const path = require('path');
const compressing = require('compressing');
const moment = require('moment');
const mongoose = require('mongoose');

const Excel = require('./../adapter/excel');
const Docx = require('./../adapter/docx');
const functionRelations = require('./../../../function-relations');
const invokeRelations = require('./../../../invoke-relations');
const functionModel = require('./../../../functions');
const files = require('./../../../files');
const fileElements = require('./../../../file-elements');
const tests = require('./../../../tests');
const { kind: fileKind } = require('./../../../files');
const logger = require('../../../logger');
const utility = require('./../../../utility');
const ParentReporter = require('./../default-report/index');
const ProjectVersionSpliter = require('../../project-version-spliter');
const ProgressIndicator = require('../../../progress-indicator');
const { versionType } = require('../../../project-versions');
const { type: taskType } = require('./../../../tasks');
const Task = require('./../../../mongo/task');
const File = require('./../../../mongo/file');
const classCreator = require('./transformer');

class DefaultReporter extends ParentReporter {
  constructor(versionId, locale, version = null, project = null, creator = null, outputDir = null, format, complete) {
    super(versionId, locale, version, project, creator, outputDir, format, complete);
    this.baseDir = __dirname;
    this.reportTime = moment().format('YYYY年MM月');
    this.funcTestResultStatistic = {};
    this.enabledCoverageKind = [];
  }

  /**
   * 报告生成
   */
  async generate(progressIndicator = new ProgressIndicator()) {
    const fileName = `软件${this.isIntegrationTest() ? '集成' : '单元'}测试报告`;
    const result = {
      cachePath: this.outputDir,
    };
    const toolInfo = require('../../../../../package.json');
    const toolVersion = toolInfo.version;
    ['statement', 'branch', 'condition', 'decision', 'mcdc', 'invokedFunc', 'invoke'].forEach((key) => {
      if (this.version.coverages & tests.coverageKind[key]) {
        const o = {
          key,
          coverageKey: `${key}Coverage`,
          totalKey: `${key}Total`,
          coveredKey: `${key}Covered`,
          name: this.i18n.__(`tests.${key}Coverage`),
        };
        this.enabledCoverageKind.push(o);
      }
    });
    const [filesProgressIndicator, xlsxProgressIndictor] = progressIndicator.split([0.5, 0.5]);
    const { versionPath } = await this.spliteByProjectVersion();
    this.versionDir = versionPath;
    const reportPath = `${path.join(this.versionDir, fileName)}`;
    const [files, tasks] = await Promise.all([
      this.spliteByFile(filesProgressIndicator),
      Task.find({ versionId: this.version._id }, ['type']),
    ]);
    const [functionsOverview] = await this.getDataAdapter().getFunctionsOverview(this.versionId, { pageSize: 999999 });
    let testCountTotal = 0;
    functionsOverview.forEach((overview, index) => {
      overview.index = index + 1;
      overview.coverageInfo = [];
      this.enabledCoverageKind.forEach((coverageKind) => {
        let coverage = '-';
        if (overview[coverageKind.totalKey]) {
          coverage = overview[coverageKind.coveredKey]
            / overview[coverageKind.totalKey];
          coverage = `${Math.round(coverage * 10000) / 100}%`;
        }
        const o = {
          coverage,
        };
        overview.coverageInfo.push(o);
      });
      testCountTotal += overview.testCount;
      overview.passedCount = 0;
      overview.failedCount = overview.testCount;
      if (this.funcTestResultStatistic[overview._id.toString()]) {
        overview.passedCount = this.funcTestResultStatistic[overview._id.toString()].passedCount;
        overview.failedCount = overview.testCount - overview.passedCount;
      }
      overview.commentRate = overview.numberOfLines ? Math.round((overview.commentLines / overview.numberOfLines) * 10000) / 100 : 0;
    });
    await this.generateXlsx(functionsOverview, reportPath, xlsxProgressIndictor);
    xlsxProgressIndictor.finish();
    const testcasesInvokeCoveredMap = new Map();
    functionsOverview.forEach((func) => {
      if (!func.invokePaths) {
        return;
      }
      func.invokePaths.forEach((paths) => {
        if (!paths) {
          return;
        }
        const { name, testcases } = paths;
        testcases.forEach((id) => {
          if (!testcasesInvokeCoveredMap.has(id)) {
            testcasesInvokeCoveredMap.set(id, []);
          }
          testcasesInvokeCoveredMap.get(id).push(name);
        });
      });
    });
    await this.generateDocxReport(
      {
        projectName: this.project.projectName,
        versionName: this.version.versionName,
        toolVersion,
        enabledCoverageKind: this.enabledCoverageKind,
        language: this.version.language,
        funcsOverview: functionsOverview,
        isIntegrationTest: this.isIntegrationTest(),
        files: files.filter((file) => {
          file.functions.forEach((func) => {
            func.tests.forEach((test) => {
              test.expectedInvokePath = testcasesInvokeCoveredMap.has(test.testId.toString())
                ? testcasesInvokeCoveredMap.get(test.testId.toString()).map((path) => `覆盖${path}`).join(',')
                : '';
            });
          });
          return file.functions.length > 0;
        }),
        reportTime: this.reportTime,
        reportDate: moment().format('YYYY年MM月DD日'),
        tester: this.creator.username,
        compiler: this.version['project type'],
        testCountTotal,
        testType: this.isIntegrationTest() ? '集成' : '单元',
        testTypeCode: this.isIntegrationTest() ? 'IT' : 'UT',
        tasks: tasks.map((task) => {
          let name = '';
          switch (task.type) {
            case taskType.parse:
              name = '源码解析';
              break;
            case taskType.testCaseGenerate:
              name = '生成用例';
              break;
            case taskType.testCaseRun:
              name = '执行用例';
              break;
            case taskType.autoTest:
              name = '一键测试';
              break;
            case taskType.autoRegression:
              name = '一键回归';
              break;
            case taskType.autoTestAndFilter:
              name = '一键补充测试';
              break;
            default:
              return null;
          }
          return {
            name,
            user: this.creator.username,
            date: moment(mongoose.Types.ObjectId(task._id).getTimestamp()).format('YYYY-MM-DD HH:mm:ss'),
          };
        }).filter((task) => task),
      },
      reportPath
    );
    result.reportPath = path.join(this.outputDir, `${fileName}.zip`);
    const reportsDir = path.join(this.outputDir, this.project.projectName);
    await compressing.zip.compressDir(reportsDir, result.reportPath);
    return result;
  }

  async spliteByFile(progressIndicator) {
    await fs.ensureDir(this.versionDir);
    const sourceFileExts = [];
    const languages = { ...this.version.languages, headers: this.version.headers };
    Object.keys(languages).forEach((key) => {
      if (languages[key].suffixes) {
        sourceFileExts.push(...languages[key].suffixes);
      }
    });

    const resFiles = new Map();
    const filter = {
      versionId: this.versionId,
      $or: [
        { kind: fileKind.file },
        { kind: fileKind.fileOutsideProject },
      ],
      $and: [{ fileName: { $nin: ['.gitignore', '.smartrocket'] } }],
    };
    if (sourceFileExts && sourceFileExts.length > 0) {
      filter.$or.push({
        exname: { $in: sourceFileExts },
      });
    }
    const allFiles = await File.find(filter, ['_id', 'fileName', 'path']).lean();
    allFiles.forEach((file) => {
      resFiles.set(file._id.toString(), file);
    });
    return this.handleFiles([...resFiles.values()], this.versionDir, progressIndicator);
  }

  async handleFiles(files, versionDir, progressIndicator) {
    return utility.arrayChunkOperation(files, 1, async (file) => {
      const result = await this.handleFile(file, versionDir);
      if (progressIndicator) {
        progressIndicator.advance(1 / files.length);
      }
      return result;
    });
  }

  getTestExpectedResultDetails(testcaseId, functionCFG) {
    const { branches, decisions, nodes } = functionCFG;
    if (branches
      && Object.keys(branches).length > 0
      && nodes
      && Object.keys(nodes).length > 0
      && decisions
      && Object.keys(decisions).length > 0) {
      const branchExprs = [];
      Object.keys(branches).forEach((branchKey) => {
        const branch = branches[branchKey];
        if (branch.decisionId === undefined || !decisions[branch.decisionId]) {
          return;
        }
        const { expr } = decisions[branch.decisionId];
        Object.keys(branch.branches).forEach((nodeId) => {
          const branchNode = branch.branches[nodeId];
          if (branchNode
            && branchNode.kind === 'BOOLEAN'
            && branchNode.testIds
            && branchNode.testIds[testcaseId]
            && branchNode.testIds[testcaseId] > 0
            && nodes[nodeId]
            && nodes[nodeId].begin) {
            branchExprs.push({
              expr: `执行${expr}的${branchNode.value === '1'}分支;`,
            });
          }
        });
      });
      return branchExprs;
    }
    return [];
  }

  async handleFile(file, versionDir) {
    const res = await ProjectVersionSpliter.prototype.handleFile.call(this, file, versionDir);
    const { typeSystem, functions } = res;
    const fileSummary = DefaultReporter.getSummaryOfFile(functions);
    const errorTests = [];
    const failedTests = [];
    let errorTestIdx = 1;
    let failedTestsIdx = 1;
    const result = {
      fileName: file.fileName,
      filePath: file.path,
      summary: fileSummary,
      functions: await utility.arrayChunkOperation(
        functions.filter((func) => fileElements.isTestableElement(func.kind) && func.tests.length > 0),
        20,
        async (func) => {
          const functionCFG = await functionModel.getFunctionCFG(func._id);
          const testInfoMap = {};
          func.tests.forEach((test) => {
            testInfoMap[test._id.toString()] = {
              log: test.log,
              testMethod: test.testMethod,
            };
          });
          const result = {};
          result.enabledCoverageKindCount = this.enabledCoverageKind.length;
          result.summary = this.getSummaryOfFunction(func);
          result.tests = this.getTestDetailsOfFunction(typeSystem, func);
          result.functionName = func.functionName;
          result.description = func.description;
          result.requirementId = '';
          func.userDefinedFields.forEach((userDefinedField) => {
            if (userDefinedField.name === '需求标识') {
              result.requirementId = userDefinedField.value;
            }
          });
          result.coverageStr = result.summary.coverages.map((coverage) => `${coverage.name}：${coverage.value}`).join('  ');
          let passedCount = 0;
          const typeNameDic = {
            GN: '功能',
            JK: '接口',
            BJ: '边界',
            LJ: '逻辑',
            XN: '性能',
          };
          const testMethodDic = {
            1: '等价类划分',
            2: '边界值分析',
            3: '错误猜测',
            4: '程序插桩',
            5: '功能分解',
          };
          result.tests.forEach((test, index) => {
            test.index = (index + 1).toString().padStart(3, '0');
            switch (test.resultKind) {
              case 'succeed':
              case 'nonStrictSucceed':
                test.resultStr = '通过';
                break;
              case 'failed':
                test.resultStr = '未通过';
                break;
              case 'exception':
                test.resultStr = '执行异常';
                break;
              case 'codeDefect':
                test.resultStr = '运行时错误';
                break;
              case 'noResult':
                test.resultStr = '无结果';
                break;
              case 'unknown':
                test.resultStr = '未执行';
                break;
              default:
                test.resultStr = '';
            }
            test.typeName = `${typeNameDic[test.typeNumber.substr(0, 2)] || '功能'}测试`;
            // eslint-disable-next-line max-len
            const testMethod = testInfoMap[test.testId.toString()].testMethod !== undefined && testInfoMap[test.testId.toString()].testMethod.length > 0
              ? [...testInfoMap[test.testId.toString()].testMethod]
              : [5];
            test.testMethod = testMethod.map((method) => testMethodDic[method]).join(',');
            let newInputDetails = [];
            const newOutputDetails = [];
            const stubsInputDetails = [];
            if (test.resultKind === 'succeed' || test.resultKind === 'nonStrictSucceed' || test.resultKind === 'noResult') {
              passedCount += 1;
            } else if (test.resultKind === 'codeDefect' || test.resultKind === 'exception') {
              errorTests.push({
                // eslint-disable-next-line no-plusplus
                idx: errorTestIdx++,
                desc: testInfoMap[test.testId.toString()].log.location
                  ? `错误位置：${testInfoMap[test.testId.toString()].log.location.file} ${testInfoMap[test.testId.toString()].log.location.line}行${testInfoMap[test.testId.toString()].log.location.column}列`
                  : '',
                kind: (test.resultKind === 'codeDefect') ? 1 : 2,
                funcName: func.functionName,
                testId: test.identifier,
              });
            }
            if (utility.isArrayType(test.detail.input)) {
              let currentCategory = '';
              let variables = [];
              let maxSpan = 0;
              let idx = 0;
              let excludeStubCategoryExists = false;
              test.detail.input.forEach((input) => {
                // eslint-disable-next-line no-control-regex
                input.variable = input.variable.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
                if (input.category && input.category !== currentCategory) {
                  currentCategory = input.category;
                }
                if (currentCategory !== '桩函数') {
                  excludeStubCategoryExists = true;
                }
                if (input.variable) {
                  const currentSpan = (input.variable.length - input.variable.trimStart().length) / 2;
                  if (currentSpan <= maxSpan) {
                    variables = variables.slice(0, currentSpan - 1);
                  }
                  maxSpan = currentSpan;
                  if (input.type === 3) {
                    variables.push(`${input.variable}数组中的成员`);
                  } else if (input.type === 4) {
                    variables.push(`${input.variable}结构体中的成员`);
                  } else {
                    variables.push(input.variable);
                  }
                }
                if (input.value !== '') {
                  idx += 1;
                  newInputDetails.push({ category: currentCategory, value: `${idx}. 设置${currentCategory}${variables.join('的')}为${input.value}` });
                }
              });
              if (!this.isIntegrationTest() && excludeStubCategoryExists) {
                newInputDetails = newInputDetails.filter(({ category }) => category !== '桩函数');
              }

              currentCategory = '';
              variables = [];
              maxSpan = 0;
              test.detail.input.forEach((input) => {
                if (input.category && input.category !== currentCategory) {
                  currentCategory = input.category;
                }
                if (currentCategory !== '桩函数') {
                  return;
                }
                if (input.kind === 12 || input.kind === 61) {
                  input.variable = `桩函数参数${input.variable.trimStart()}`;
                  variables = [];
                } else if (input.kind === 11) {
                  input.variable = `桩函数返回值${input.variable.trimStart()}`;
                  variables = [];
                } else if (input.kind === 13) {
                  input.variable = `桩函数指针目标${input.variable.trimStart()}`;
                  variables = [];
                } else if (input.kind === 14) {
                  input.variable = `桩函数成员变量${input.variable.trimStart()}`;
                  variables = [];
                } else if (input.kind === 60) {
                  input.variable = `桩函数下全局变量${input.variable.trimStart()}`;
                  variables = [];
                } else if (input.kind === 10) {
                  stubsInputDetails.push({ value: `桩函数${input.variable.trimStart()}` });
                  return;
                } else if (variables.length === 0) {
                  stubsInputDetails.push({ value: input.variable });
                  return;
                }
                if (input.variable) {
                  const currentSpan = (input.variable.length - input.variable.trimStart().length) / 2;
                  if (currentSpan <= maxSpan) {
                    variables = variables.slice(0, currentSpan - 1);
                  }
                  maxSpan = currentSpan;
                  if (input.type === 3) {
                    variables.push(`${input.variable.trimStart()}数组中的成员`);
                  } else if (input.type === 4) {
                    variables.push(`${input.variable.trimStart()}结构体中的成员`);
                  } else {
                    variables.push(input.variable.trimStart());
                  }
                }
                if (input.value !== '') {
                  stubsInputDetails.push({ value: `${variables.join('的')}为${input.value}` });
                }
              });
            }
            if (utility.isArrayType(test.detail.output)) {
              let currentCategory = '';
              let variables = [];
              let maxSpan = 0;
              let idx = 0;
              test.detail.output.forEach((output) => {
                // eslint-disable-next-line no-control-regex
                output.variable = output.variable.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
                output.idx = idx + 1;
                if (test.resultKind === 'failed' && output.value !== '' && output.value !== output.result) {
                  failedTests.push({
                    // eslint-disable-next-line no-plusplus
                    idx: failedTestsIdx++,
                    desc: `${output.variable}, 实际值=${output.result}, 期望值=${output.value}`,
                    kind: 0,
                    funcName: func.functionName,
                    testId: test.identifier,
                  });
                }
                if (output.category && output.category !== currentCategory) {
                  currentCategory = output.category;
                }
                if (output.variable) {
                  const currentSpan = (output.variable.length - output.variable.trimStart().length) / 2;
                  if (currentSpan <= maxSpan) {
                    variables = variables.slice(0, currentSpan - 1);
                  }
                  maxSpan = currentSpan;
                  variables.push(output.variable);
                  if (output.type === 3) {
                    variables.push(`${output.variable}数组中的成员`);
                  } else if (output.type === 4) {
                    variables.push(`${output.variable}结构体中的成员`);
                  } else {
                    variables.push(output.variable);
                  }
                }
                output.value = output.value !== ''
                  ? output.value
                  : (output.result !== '' ? output.value : '');
                if (output.value !== '') {
                  idx += 1;
                  newOutputDetails.push({
                    value: `${idx}. 设置${currentCategory}${variables.join('的')}的预期值为${output.value}`,
                    result: `${idx}. ${currentCategory}${variables.join('的')}的实际值为${output.result}`,
                  });
                }
              });
            }
            test.detail.input = newInputDetails;
            test.detail.stubInput = stubsInputDetails;
            test.detail.output = newOutputDetails;
            test.expectedResultDetails = this.getTestExpectedResultDetails(test.testId.toString(), functionCFG);
          });
          this.funcTestResultStatistic[func._id.toString()] = { passedCount };
          return result;
        }
      ),
      errorTests,
      failedTests,
    };
    return result;
  }

  generateDocxReport(data, output) {
    const template = 'file-template';
    return (async () => {
      output = `${output}.docx`;
      const tpl = new Docx(path.join(this.baseDir, 'template', 'docx', `${template}.docx`));
      await tpl.generate(
        data,
        output,
        true
      );
      return output;
    })();
  }

  async generateXlsx(functionsOverview, reportPath, progressIndicator) {
    const enabledCoverageKind = [];
    ['statement', 'branch', 'condition', 'decision', 'mcdc'].forEach((key) => {
      if (this.version.coverages & tests.coverageKind[key]) {
        const o = {
          name: this.i18n.__(`tests.${key}Coverage`),
        };
        enabledCoverageKind.push(o);
      }
    });
    if (this.isIntegrationTest()) {
      const funcRelations = await functionRelations.getRelationsByVersion(this.version._id);
      const funcs = await functionModel.getFunctions(
        this.version._id,
        {
          kinds: [fileElements.elementKind.function, fileElements.elementKind.specialFunction],
          projection: { _id: 1, kind: 1, insideProject: 1 },
        }
      );
      const specFuncIds = new Set();
      const insideProjectFuncIds = new Set();
      funcs.forEach((func) => {
        if (func.kind === fileElements.elementKind.specialFunction) {
          specFuncIds.add(func._id.toString());
        }
        if (func.insideProject) {
          insideProjectFuncIds.add(func._id.toString());
        }
      });
      // eslint-disable-next-line no-inner-declarations
      function resc(funcId, parentCovered, relationsMap, insideProjectFuncIds, pathSet, invokePaths) {
        let flushPath = true;
        const originalPathSet = new Set([...pathSet]);
        const originalParentCovered = [...parentCovered];
        if (relationsMap.has(funcId)) {
          relationsMap.get(funcId).forEach((relation, idx) => {
            if (relation.toFuncPointer === true || relation.toFuncCtor === true) {
              return;
            }
            if (idx > 0) {
              pathSet = new Set([...originalPathSet]);
              parentCovered = [...originalParentCovered];
            }
            const testcases = relation.testCases !== undefined && relation.testCases.length > 0 ? relation.testCases.map((id) => id.toString()) : [];
            if (originalPathSet.size === 1) {
              parentCovered = testcases;
            } else {
              const testcasesSet = new Set(testcases);
              parentCovered = parentCovered.filter((id) => testcasesSet.has(id));
            }
            flushPath = false;
            if (relation.enableStub
              || !relation.toFuncId
              || pathSet.has(relation.toFuncName)
              || !insideProjectFuncIds.has(relation.toFuncId.toString())
            ) {
              pathSet.add(relation.toFuncName);
              invokePaths.push({
                name: [...pathSet].join('->'),
                depth: pathSet.size - 1,
                testcases: parentCovered,
              });
              return;
            }
            pathSet.add(relation.toFuncName);
            resc(
              relation.toFuncId.toString(),
              parentCovered,
              relationsMap,
              insideProjectFuncIds,
              pathSet,
              invokePaths
            );
          });
        }
        if (flushPath && pathSet.size > 1) {
          invokePaths.push({
            name: [...pathSet].join('->'),
            testcases: parentCovered,
            depth: pathSet.size - 1,
          });
        }
      }
      await utility.arrayChunkOperation(functionsOverview, 20, async (func) => {
        let relations = await invokeRelations.retrieveRelations(func._id, { includeCtor: false });
        if (relations.length === 0) {
          relations = await functionRelations.retrieveRelations(
            func._id,
            this.version._id,
            funcRelations
          );
          relations = JSON.parse(JSON.stringify(relations));
          relations = invokeRelations.stubFuncsByDefaultDepth(func._id, relations, specFuncIds, this.version.stubDepth);
          relations = await invokeRelations.filterRelations(func._id, relations, { includeCtor: false });
        }
        const invokePaths = [];
        const relationsMap = new Map();
        relations.forEach((relation) => {
          if (!relationsMap.has(relation.fromFuncId.toString())) {
            relationsMap.set(relation.fromFuncId.toString(), []);
          }
          relationsMap.get(relation.fromFuncId.toString()).push(relation);
        });
        resc(func._id.toString(), [], relationsMap, insideProjectFuncIds, new Set([func.functionName]), invokePaths);
        func.invokePaths = invokePaths;
        let maxDepth = 0;
        func.invokePaths.forEach((path) => {
          maxDepth = Math.max(path.depth, maxDepth);
        });
        func.depth = maxDepth;
        func.invokeCoverage = func.invokeTotal ? `${Math.round((func.invokeCovered / func.invokeTotal) * 10000) / 100}%` : '-';
      });
    }
    const excel = new Excel(this.creator.username);
    const font = { name: 'Times New Roman', size: 11 };
    const titleFont = { name: 'Times New Roman', size: 11, bold: true };
    if (this.isIntegrationTest()) {
      excel.addSheet('函数覆盖率表');
      excel.setColumn('A', { width: 15 });
      excel.setColumn('B', { width: 15 });
      excel.setColumn('C', { width: 22 });
      excel.setColumn('D', { width: 5 });
      excel.setColumn('E', { width: 7 });
      excel.setColumn('F', { width: 70 });
      excel.setColumn('G', { width: 5 });
      excel.setColumn('H', { width: 10 });
      excel.setColumn('I', { width: 7 });
      excel.setColumn('J', { width: 45 });
      excel.setCell('A1', { value: '模块', font: titleFont });
      excel.setCell('B1', { value: '源文件名', font: titleFont });
      excel.setCell('C1', { value: '函数名', font: titleFont });
      excel.setCell('D1', { value: '顶层函数', font: titleFont });
      excel.setCell('E1', { value: '调用覆盖率', font: titleFont });
      excel.setCell('F1', { value: '依次调用的路径', font: titleFont });
      excel.setCell('G1', { value: '用例个数', font: titleFont });
      excel.setCell('H1', { value: '测试人员', font: titleFont });
      excel.setCell('I1', { value: '路径是否覆盖', font: titleFont });
      excel.setCell('J1', { value: '备注', font: titleFont });
      excel.setRow(1, { height: 18 });
      let fileStart = 2;
      let lastFileName = '';
      let line = 2;
      let funcStart;
      functionsOverview.forEach((func) => {
        funcStart = line;
        if (func.invokePaths.length === 0) {
          func.invokePaths.push(null);
        }
        func.invokePaths.forEach((path) => {
          excel.setRow(line, { height: 18 });
          excel.setCell(`A${line}`, { value: this.project.projectName, font });
          excel.setCell(`B${line}`, { value: func.fileName, font });
          excel.setCell(`C${line}`, { value: func.functionName, font });
          excel.setCell(`D${line}`, { value: func.fanIn === 0 ? '是' : '否', font });
          excel.setCell(`E${line}`, { value: func.invokeCoverage, font });
          excel.setCell(`F${line}`, { value: path ? path.name : '', font });
          excel.setCell(`G${line}`, { value: func.fanIn === 0 && func.fanOut === 0 ? 0 : func.testCount, font });
          excel.setCell(`H${line}`, { value: this.creator.username, font });
          excel.setCell(`I${line}`, { value: path ? (path.testcases.length > 0 ? '是' : '否') : '', font });
          excel.setCell(
            `J${line}`,
            {
              value: func.fanOut === 0
                ? (func.fanIn === 0 ? '未被调用，也无调用函数' : '被调用，但无调用函数')
                : '',
              font,
            }
          );
          line += 1;
        });
        if (func.invokePaths.length > 1) {
          excel.mergeCells(`C${funcStart}`, `C${funcStart + func.invokePaths.length - 1}`);
          excel.mergeCells(`D${funcStart}`, `D${funcStart + func.invokePaths.length - 1}`);
          excel.mergeCells(`E${funcStart}`, `E${funcStart + func.invokePaths.length - 1}`);
          excel.mergeCells(`G${funcStart}`, `G${funcStart + func.invokePaths.length - 1}`);
        }
        if (func.fileName !== lastFileName && lastFileName !== '') {
          excel.mergeCells(`B${fileStart}`, `B${funcStart - 1}`);
          fileStart = funcStart;
        }
        lastFileName = func.fileName;
        progressIndicator.advance(1 / functionsOverview.length);
      });
      excel.mergeCells(`B${fileStart}`, `B${line - 1}`);
      excel.mergeCells('A2', `A${line - 1}`);
    } else {
      excel.addSheet('单元测试覆盖率');
      excel.setColumn('A', { width: 7 });
      excel.setColumn('B', { width: 24 });
      excel.setColumn('C', { width: 40 });
      excel.setColumn('D', { width: 7 });
      excel.setColumn('E', { width: 7 });
      excel.setColumn('F', { width: 7 });
      excel.setColumn('G', { width: 7 });
      excel.setColumn('H', { width: 7 });
      excel.setColumn('I', { width: 7 });
      excel.setColumn('J', { width: 7 });
      excel.setColumn('K', { width: 7 });
      excel.setCell('A1', { value: '接口统计表', font: titleFont });
      excel.mergeCells('A1', `${String.fromCharCode(enabledCoverageKind.length + 70)}1`);
      excel.setRow(1, { height: 18 });
      excel.setCell('A2', { value: '文件名', font: titleFont });
      excel.mergeCells('A2', 'B2');
      excel.setRow(2, { height: 18 });
      excel.setCell('A3', { value: '配置项', font: titleFont });
      excel.setCell('B3', { value: '配置项内文件', font: titleFont });
      excel.setCell('C2', { value: '函数名', font: titleFont });
      excel.mergeCells('C2', 'C3');
      excel.setRow(3, { height: 18 });
      excel.setCell('D2', { value: '覆盖测试', font: titleFont });
      excel.mergeCells('D2', `${String.fromCharCode(enabledCoverageKind.length + 67)}2`);
      enabledCoverageKind.forEach((coverage, index) => {
        excel.setCell(`${String.fromCharCode(index + 68)}3`, { value: coverage.name, font: titleFont });
      });
      const dynamicHeader = {
        testCount: `${String.fromCharCode(enabledCoverageKind.length + 68)}`,
        tester: `${String.fromCharCode(enabledCoverageKind.length + 69)}`,
        comment: `${String.fromCharCode(enabledCoverageKind.length + 70)}`,
      };
      excel.setCell(`${dynamicHeader.testCount}2`, { value: '用例个数', font: titleFont });
      excel.mergeCells(`${dynamicHeader.testCount}2`, `${dynamicHeader.testCount}3`);
      excel.setCell(`${dynamicHeader.tester}2`, { value: '测试人员', font: titleFont });
      excel.mergeCells(`${dynamicHeader.tester}2`, `${dynamicHeader.tester}3`);
      excel.setCell(`${dynamicHeader.comment}2`, { value: '备注', font: titleFont });
      excel.mergeCells(`${dynamicHeader.comment}2`, `${dynamicHeader.comment}3`);
      let fileStart = 4;
      let lastFileName = '';
      functionsOverview.forEach((func, i) => {
        excel.setRow(i + 4, { height: 18 });
        excel.setCell(`A${i + 4}`, { value: this.project.projectName, font });
        excel.setCell(`B${i + 4}`, { value: func.fileName, font });
        excel.setCell(`C${i + 4}`, { value: func.functionName, font });
        enabledCoverageKind.forEach((coverage, index) => {
          excel.setCell(`${String.fromCharCode(index + 68)}${i + 4}`, { value: func.coverageInfo[index].coverage, font });
        });
        excel.setCell(`${dynamicHeader.testCount}${i + 4}`, { value: func.testCount, font });
        excel.setCell(`${dynamicHeader.tester}${i + 4}`, { value: this.creator.username, font });
        excel.setCell(`${dynamicHeader.comment}${i + 4}`, { value: '', font });
        if (func.fileName !== lastFileName && lastFileName !== '') {
          excel.mergeCells(`B${fileStart}`, `B${i + 3}`);
          fileStart = i + 4;
        }
        lastFileName = func.fileName;
        progressIndicator.advance(1 / functionsOverview.length);
      });
      excel.mergeCells(`B${fileStart}`, `B${functionsOverview.length + 3}`);
      excel.mergeCells('A4', `A${functionsOverview.length + 3}`);
    }
    return excel.writeFile(`${reportPath}.xlsx`);
  }

  /**
   * 获取用例列表详细信息
   * @param {Object} typeSystem 类型系统
   * @param {Object} func 函数信息
   */
  getTestDetailsOfFunction(typeSystem, func) {
    const resTests = [];
    func.tests.sort((a, b) => {
      const c1Time = a.group ? mongoose.Types.ObjectId(a.weight.substr(0, a.weight.length - 4)).getTimestamp().getTime()
        : a._id.getTimestamp().getTime();
      const c2Time = b.group ? mongoose.Types.ObjectId(b.weight.substr(0, b.weight.length - 4)).getTimestamp().getTime()
        : b._id.getTimestamp().getTime();
      if (typeof a.group === typeof b.group) {
        return a.weight > b.weight ? 1 : -1;
      }
      return c1Time > c2Time ? 1 : -1;
    });
    const funcVariables = functionModel.getFunctionVariableJson(func.variables);
    if (!this.isIntegrationTest()) {
      typeSystem = files.getTypesJson(func.fileId, typeSystem);
    }
    func.tests.forEach((test) => {
      const testCase = tests.getJson(test, this.i18n);
      const ReportTestCaseTransformer = classCreator(this.isIntegrationTest() && 'integrationTest');
      const transformer = new ReportTestCaseTransformer(
        testCase,
        funcVariables,
        typeSystem,
        this.i18n
      );
      try {
        transformer.transform(this.complete);
      } catch (error) {
        logger.error(`testCase transform error: fileId: ${func.fileId} function name: ${func.functionName} test case number: ${test.number}:`, error);
        return;
      }

      const { coverages } = this.version;
      let statementCoverage; let branchCoverage; let conditionCoverage; let mcdcCoverage;
      let invokeCoverage; let invokedFuncCoverage; let decisionCoverage;
      if ((coverages & tests.coverageKind.statement) > 0) {
        statementCoverage = '-';
        if (func.statementTotal && !this.isValidResultKind(test.resultKind)) {
          statementCoverage = '0%';
        } else if (func.statementTotal) {
          statementCoverage = test.statementCovered / func.statementTotal;
          statementCoverage = `${Math.round(statementCoverage * 10000) / 100}%`;
        }
      }
      if ((coverages & tests.coverageKind.branch) > 0) {
        branchCoverage = '-';
        if (func.branchTotal && !this.isValidResultKind(test.resultKind)) {
          branchCoverage = '0%';
        } else if (func.branchTotal) {
          branchCoverage = test.branchCovered / func.branchTotal;
          branchCoverage = `${Math.round(branchCoverage * 10000) / 100}%`;
        }
      }
      if ((coverages & tests.coverageKind.condition) > 0) {
        conditionCoverage = '-';
        if (func.conditionTotal && !this.isValidResultKind(test.resultKind)) {
          conditionCoverage = '0%';
        } else if (func.conditionTotal) {
          conditionCoverage = test.conditionCovered / func.conditionTotal;
          conditionCoverage = `${Math.round(conditionCoverage * 10000) / 100}%`;
        }
      }
      if ((coverages & tests.coverageKind.decision) > 0) {
        decisionCoverage = '-';
        if (func.decisionTotal && !this.isValidResultKind(test.resultKind)) {
          decisionCoverage = '0%';
        } else if (func.decisionTotal) {
          decisionCoverage = test.decisionCovered / func.decisionTotal;
          decisionCoverage = `${Math.round(decisionCoverage * 10000) / 100}%`;
        }
      }
      if ((coverages & tests.coverageKind.mcdc) > 0) {
        mcdcCoverage = '-';
        if (func.mcdcTotal && !this.isValidResultKind(test.resultKind)) {
          mcdcCoverage = '0%';
        } else if (func.mcdcTotal) {
          mcdcCoverage = test.mcdcCovered / func.mcdcTotal;
          mcdcCoverage = `${Math.round(mcdcCoverage * 10000) / 100}%`;
        }
      }
      if ((coverages & tests.coverageKind.invoke) > 0) {
        invokeCoverage = '-';
        if (func.invokeTotal && !this.isValidResultKind(test.resultKind)) {
          invokeCoverage = '0%';
        } else if (func.invokeTotal) {
          invokeCoverage = test.invokeCovered / func.invokeTotal;
          invokeCoverage = `${Math.round(invokeCoverage * 10000) / 100}%`;
        }
      }
      if ((coverages & tests.coverageKind.invokedFunc) > 0) {
        invokedFuncCoverage = '-';
        if (func.invokedFuncTotal && !this.isValidResultKind(test.resultKind)) {
          invokedFuncCoverage = '0%';
        } else if (func.invokedFuncTotal) {
          invokedFuncCoverage = test.invokedFuncCovered / func.invokedFuncTotal;
          invokedFuncCoverage = `${Math.round(invokedFuncCoverage * 10000) / 100}%`;
        }
      }
      let annotations;
      test.udfs.forEach((udf) => {
        if (udf.name === '用例注释') {
          annotations = udf.value;
        }
      });
      resTests.push({
        testId: testCase.testId,
        index: test.number,
        description: testCase.description,
        identifier: testCase.identifier,
        requirementInfo: testCase.requirementInfo,
        typeName: testCase.typeName,
        typeNumber: testCase.typeNumber,
        resultKind: test.resultKindName,
        udfs: test.udfs,
        detail: {
          prelude: transformer.prelude,
          input: transformer.input,
          output: transformer.output,
          result: transformer.result,
        },
        isIntegrationTest: this.isIntegrationTest(),
        group: test.group ? test.group.map((num) => `${this.i18n.__('tests.group')}${num} / `).join('') : '',
        statementCoverage,
        branchCoverage,
        conditionCoverage,
        decisionCoverage,
        mcdcCoverage,
        invokeCoverage,
        invokedFuncCoverage,
        annotations,
        execCount: testCase.execCount,
      });
    });
    return resTests;
  }

  static versionTypeSupport(vType) {
    return vType === versionType.unitTest || vType === versionType.integrationTest;
  }
}

/**
 * Export model definition object.
 */
module.exports = DefaultReporter;
